using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;

using AGG.Transform;
using AGG.Image;

namespace AGG.UI
{
    public abstract class GuiHalSurface : GUIWidget
    {
        public delegate void IdleEventHandler(object sender);
        public event IdleEventHandler Idle;
        public int milliSecondsToSleepEachIdle = 1;

        [Flags]
        public enum CreateFlags
        {
            None = 0,
            Resizable = 1,
            FullScreen = 2,
        };

        public enum PixelFormat
        {
            PixelFormatBgr24,
            PixelFormatBgra32,
            PixelFormatRgbaFloat
        };

        public abstract string Caption
        {
            get;
            set;
        }

        public int MilliSecondsToSleepEachIdle
        {
            get
            {
                return milliSecondsToSleepEachIdle;
            }

            set
            {
                milliSecondsToSleepEachIdle = value;
            }
        }

        public abstract void OnResize(int sx, int sy);
        public abstract void Init(int Width, int Height, GuiHalSurface.CreateFlags flags, GuiHalSurface.PixelFormat pixelFormat);
        public abstract void Run();
    
        protected ImageFormats m_format;
        protected int m_bpp;
        protected CreateFlags m_window_flags;
        protected int initialWidth;
        protected int initialHeight;

        [Flags]
        public enum WindowFlags
        {
            None = 0,
            Resizable = 1,
            KeepAspectRatio = 2,
            UseOpenGL = 4,
        };

        public virtual void OnInitialize()
        {
        }

        public void OnIdle()
        {
            if (Idle != null)
            {
                Idle(this);
            }
        }

        //-----------------------------------------------------------pix_format_e
        // Possible formats of the rendering buffer. Initially I thought that it's
        // reasonable to create the buffer and the rendering functions in 
        // accordance with the native pixel format of the system because it 
        // would have no overhead for pixel format conversion. 
        // But eventually I came to a conclusion that having a possibility to 
        // convert pixel formats on demand is a good idea. First, it was X11 where 
        // there lots of different formats and visuals and it would be great to 
        // render everything in, say, RGB-24 and display it automatically without
        // any additional efforts. The second reason is to have a possibility to 
        // debug renderers for different pixel formats and colorspaces having only 
        // one computer and one system.
        //
        // This stuff is not included into the basic AGG functionality because the 
        // number of supported pixel formats (and/or colorspaces) can be great and 
        // if one needs to add new format it would be good only to add new 
        // rendering files without having to modify any existing ones (a general 
        // principle of incapsulation and isolation).
        //
        // Using a particular pixel format doesn't obligatory mean the necessity
        // of software conversion. For example, win32 API can natively display 
        // gray8, 15-bit RGB, 24-bit BGR, and 32-bit BGRA formats. 
        // This list can be (and will be!) extended in future.
        public enum ImageFormats
        {
            pix_format_undefined = 0,  // By default. No conversions are applied 
            pix_format_bw,             // 1 bit per color B/W
            pix_format_gray8,          // Simple 256 level grayscale
            pix_format_gray16,         // Simple 65535 level grayscale
            pix_format_rgb555,         // 15 bit rgb. Depends on the byte ordering!
            pix_format_rgb565,         // 16 bit rgb. Depends on the byte ordering!
            pix_format_rgbAAA,         // 30 bit rgb. Depends on the byte ordering!
            pix_format_rgbBBA,         // 32 bit rgb. Depends on the byte ordering!
            pix_format_bgrAAA,         // 30 bit bgr. Depends on the byte ordering!
            pix_format_bgrABB,         // 32 bit bgr. Depends on the byte ordering!
            pix_format_rgb24,          // R-G-B, one byte per color component
            pix_format_bgr24,          // B-G-R, native win32 BMP format.
            pix_format_rgba32,         // R-G-B-A, one byte per color component
            pix_format_argb32,         // A-R-G-B, native MAC format
            pix_format_abgr32,         // A-B-G-R, one byte per color component
            pix_format_bgra32,         // B-G-R-A, native win32 BMP format
            pix_format_rgb48,          // R-G-B, 16 bits per color component
            pix_format_bgr48,          // B-G-R, native win32 BMP format.
            pix_format_rgba64,         // R-G-B-A, 16 bits byte per color component
            pix_format_argb64,         // A-R-G-B, native MAC format
            pix_format_abgr64,         // A-B-G-R, one byte per color component
            pix_format_bgra64,         // B-G-R-A, native win32 BMP format
            pix_format_rgba_float,       // R-G-B-A, all values stored as floats

            end_of_pix_formats
        };

        public static int GetBitDepthForPixelFormat(ImageFormats pixelFormat)
        {
            switch(pixelFormat)
            {
                case ImageFormats.pix_format_bgr24:
                case ImageFormats.pix_format_rgb24:
                    return 24;

                case ImageFormats.pix_format_bgra32:
                case ImageFormats.pix_format_rgba32:
                    return 32;

                case ImageFormats.pix_format_rgba_float:
                    return 32 * 4;

                default:
                    throw new System.NotImplementedException();
            }
        }

        // format - see enum pix_format_e {};
        // flip_y - true if you want to have the Y-axis flipped vertically.
        public GuiHalSurface(ImageFormats format)
        {
            m_format = format;
            m_bpp = GetBitDepthForPixelFormat(format);
        }

        public class OpenFileDialogParams
        {
            String filterDescription;
            String filterExtension;
            String initialDirectory;
            
            String fileName;

            public String FilterDescription
            {
                get { return filterDescription; }
                set { filterDescription = value; }
            }

            public String FilterExtension
            {
                get { return filterExtension; }
                set { filterExtension = value; }
            }

            public String InitialDirectory
            {
                get { return initialDirectory; }
                set { initialDirectory = value; }
            }

            public String FileName
            {
                get { return fileName; }
                set { fileName = value; }
            }

            public OpenFileDialogParams(String fileTypeFilterDescription, String fileExtensionFilter)
                : this(fileTypeFilterDescription, fileExtensionFilter, null)
            {
                
            }

            public OpenFileDialogParams(String fileTypeFilterDescription, String fileExtensionFilter,
                String initialDirectory)
            {
                this.filterDescription = fileTypeFilterDescription;
                this.filterExtension = fileExtensionFilter;
                this.initialDirectory = initialDirectory;
            }
        }

        public abstract Stream OpenFileDialog(OpenFileDialogParams openParams);

        public static String ClipboardGetText()
        {
            throw new NotImplementedException();
            // System.Windows.Forms.Clipboard.GetText()
        }

        public static void ClipboardSetText(String text)
        {
            throw new NotImplementedException();
            // System.Windows.Forms.Clipboard.SetText();
        }

        public static bool ClipboardContainsText()
        {
            throw new NotImplementedException();
            // System.Windows.Forms.Clipboard.ContainsText()
        }

        // The very same parameters that were used in the constructor
        public ImageFormats format() { return m_format; }

        public int bpp() { return m_bpp; }

        abstract public void Close();
        abstract public void OnClosing(out bool CancelClose);

        abstract public void OnControlChanged();

        public double width() { return Bounds.Width; }
        public double height() { return Bounds.Height; }
        public double initial_width() { return initialWidth; }
        public double initial_height() { return initialHeight; }
        public CreateFlags window_flags() { return m_window_flags; }

        // Get raw display handler depending on the system. 
        // For win32 its an HDC, for other systems it can be a pointer to some
        // structure. See the implementation files for detals.
        // It's provided "as is", so, first you should check if it's not null.
        // If it's null the raw_display_handler is not supported. Also, there's 
        // no guarantee that this function is implemented, so, in some 
        // implementations you may have simply an unresolved symbol when linking.
        //public void* raw_display_handler();


        // display message box or print the message to the console 
        // (depending on implementation)
        abstract public void ShowSystemMessage(String msg);

        // Stopwatch functions. Function elapsed_time() returns time elapsed 
        // since the latest start_timer() invocation in millisecods. 
        // The resolutoin depends on the implementation. 
        // In Win32 it uses QueryPerformanceFrequency() / QueryPerformanceCounter().
        //abstract public void start_timer();
        //abstract public double elapsed_time();
        abstract public void start_timer();
        abstract public double elapsed_time();

        // Get the full file name. In most cases it simply returns
        // file_name. As it's appropriate in many systems if you open
        // a file by its name without specifying the path, it tries to 
        // open it in the current directory. The demos usually expect 
        // all the supplementary files to be placed in the current 
        // directory, that is usually coincides with the directory where
        // the the executable is. However, in some systems (BeOS) it's not so. 
        // For those kinds of systems full_file_name() can help access files 
        // preserving commonly used policy.
        // So, it's a good idea to use in the demos the following:
        // FILE* fd = fopen(full_file_name("some.file"), "r"); 
        // instead of
        // FILE* fd = fopen("some.file", "r"); 
        //abstract public String full_file_name(String file_name);

        // Sorry, I'm too tired to describe the private 
        // data members. See the implementations for different
        // platforms for details.
    }

    // interface for a GUI hardware abstraction layer
    public interface IGuiFactory
    {
        string GetFactoryName();
        GuiHalSurface CreateSurface(int Width, int Height, GuiHalSurface.CreateFlags flags, GuiHalSurface.PixelFormat pixelFormat);
    }

    // the static class used to 
    public static class GuiHalFactory
    {
        static IGuiFactory halGuiFactory;

        public enum KnownGuiFactoriesIndexes
        {
            WindowsFormsBitmap,
            WindowsFormsOpenGL,
        }

        public static string[] KnownGuiFactoriesNames  = 
        {
            "WindowsFormsBitmapBackendGuiFactory",
            "WindowsFormsOpenGLBackendGuiFactory",
        };

        public static void SetGuiBackend(KnownGuiFactoriesIndexes knownGuiIndex)
        {
            SetGuiBackend(KnownGuiFactoriesNames[(int)knownGuiIndex]);
        }

        public static void SetGuiBackend(string factoryNameToUse)
        {
            List<IGuiFactory> factoryList = GetPlugins(System.IO.Path.GetFullPath("."));

            foreach (IGuiFactory factory in factoryList)
            {
                if (factoryNameToUse == factory.GetFactoryName())
                {
                    SetGuiBackend(factory);
                    return;
                }
            }
        }

        public static void SetGuiBackend(IGuiFactory factoryToUse)
        {
            if (GuiHalFactory.halGuiFactory != null)
            {
                throw new NotSupportedException("You can only set the graphics target one time in an application.");
            }

            GuiHalFactory.halGuiFactory = factoryToUse;
        }

        public static GuiHalSurface CreatePrimarySurface(int width, int height, GuiHalSurface.CreateFlags flags, GuiHalSurface.PixelFormat pixelFormat)
        {
            if (halGuiFactory == null)
            {
                throw new NotSupportedException("You must call 'SetGuiBackend' with a GuiFactory before you can create any surfaces");
            }

            return halGuiFactory.CreateSurface(width, height, flags, pixelFormat);
        }

        public static List<IGuiFactory> GetPlugins(string folder)
        {
            //string[] files = Directory.GetFiles(folder, "*_HalFactory.dll");
            string[] files = Directory.GetFiles(folder, "*.dll");

            List<IGuiFactory> factoryList = new List<IGuiFactory>();

            foreach (string file in files)
            {
                try
                {
                    Assembly assembly = Assembly.LoadFile(file);

                    foreach (Type type in assembly.GetTypes())
                    {

                        if (!type.IsClass || !type.IsPublic)
                        {
                            continue;
                        }

                        Type[] interfaces = type.GetInterfaces();

                        if (((IList)interfaces).Contains(typeof(IGuiFactory)))
                        {
                            factoryList.Add((IGuiFactory)Activator.CreateInstance(type));
                        }
                    }
                }
                catch (BadImageFormatException badImageFormat)
                {
                }
            }

            return factoryList;

        }
    }
}
